// Object.h
// (c) 2008, Charles Lechasseur
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#ifndef RRO_UTILS_COM_OBJECT_H
#define RRO_UTILS_COM_OBJECT_H

#pragma once

#include <windows.h>
#include "COM/Server.h"


namespace RRO {
namespace COM
{
    //
    // Base class for COM objects implementing a single-inheritance hierarchy. This base class implements
    // thread-safe refcounting as well as QI support for IUnknown. To create the full hierarchy, see
    // the Object class below.
    //
    // You can use ObjectBase if you simply want an object implementing IUnknown:
    // class Foo : public ObjectBase<IUnknown> { ... };
    //
    template<class IntfT>
    class ObjectBase : public IntfT
    {
    public:
                            //
                            // Constructor
                            //
                            // @param p_RegisterInServer Whether to register the object in the COM server.
                            //                           Defaults to true; be CAREFUL when setting this parameter
                            //                           to false, since it might cause problems if the COM
                            //                           server terminates while objects are still active.
                            //
        explicit            ObjectBase(bool p_RegisterInServer = true)
	                            : m_RefCount(1),
                                  m_RegisteredInServer(p_RegisterInServer)
                            {
                                if (p_RegisterInServer) {
                                    // Tell server about active COM object.
                                    Server::Instance().AddObject();
                                }
                            }

	                        //
	                        // Destructor.
	                        //
	    virtual             ~ObjectBase(void)
	                        {
                                if (m_RegisteredInServer) {
                                    // Tell server object is no longer active.
                                    Server::Instance().RemoveObject();
                                }
	                        }

        // IUnknown methods

                            //
                            // Queries object for the given interface. This class supports IUnknown only.
                            //
        STDMETHOD(QueryInterface)(REFIID p_IID, void** p_ppObject)
                            {
		                        HRESULT res = E_UNEXPECTED;

		                        if (p_ppObject == 0) {
			                        res = E_INVALIDARG;
		                        } else if (::IsEqualIID(p_IID, IID_IUnknown)) {
			                        *p_ppObject = static_cast<IUnknown*>(this);
			                        res = S_OK;
		                        } else {
			                        *p_ppObject = 0;
			                        res = E_NOINTERFACE;
		                        }

		                        return res;
                            }

	                        //
	                        // Increments the refcount of this object in a thread-safe manner.
	                        //
        STDMETHOD_(ULONG, AddRef)(void)
                            {
                                return ::InterlockedIncrement(reinterpret_cast<LONG*>(&m_RefCount));
                            }

	                        //
	                        // Decrements the refcount of this object in a thread-safe manner.
	                        // If this was the last reference, the object is destroyed.
	                        //
        STDMETHOD_(ULONG, Release)(void)
                            {
		                        unsigned long resultingRefCount =
                                    ::InterlockedDecrement(reinterpret_cast<LONG*>(&m_RefCount));
		                        if (resultingRefCount == 0) {
			                        delete this;
		                        }
		                        return resultingRefCount;
                            }

    protected:
                            //
                            // Checks if this object is registered in the COM server. If we are,
                            // we will unregister at the end of the object lifecycle.
                            //
        bool                RegisteredInServer(void) const
                            {
                                return m_RegisteredInServer;
                            }

    private:
        // Reference count
        unsigned long       m_RefCount;
        // Whether we're registered in the COM server.
        bool                m_RegisteredInServer;

        // THESE METHODS ARE NOT IMPLEMENTED.
                            ObjectBase(const ObjectBase&);
        ObjectBase&         operator=(const ObjectBase&);
    };

    //
    // Class for implementing COM objects with single-inheritance hierarchies. Used with ObjectBase,
    // this class can support QI calls for all interfaces in the chain.
    //
    // For example, if you wanted to create a COM object that implements IContextMenu2:
    // class Foo : public Object<
    //                      Object<
    //                        ObjectBase<IContextMenu2>,
    //                        IID_IContextMenu, IContextMenu>,
    //                      IID_IContextMenu2,
    //                      IContextMenu2>
    //
    template<class BaseT, REFIID IntfIID, class IntfT>
    class Object : public BaseT
    {
    public:
                            //
                            // Constructor
                            //
                            // @param p_RegisterInServer Whether to register the object in the COM server.
                            //                           Defaults to true; be CAREFUL when setting this parameter
                            //                           to false, since it might cause problems if the COM
                            //                           server terminates while objects are still active.
                            //
        explicit            Object(bool p_RegisterInServer = true)
                                : BaseT(p_RegisterInServer)
                            {
                            }

                            //
                            // Destructor
                            //
        virtual             ~Object(void)
                            {
                            }

        // IUnknown method overrides

                            //
                            // Query interface implementation that supports our interface,
                            // otherwise goes down in the chain.
                            //
        STDMETHOD(QueryInterface)(REFIID p_IID, void** p_ppObject)
                            {
                                HRESULT res = E_UNEXPECTED;

                                if (p_ppObject == 0) {
                                    res = E_INVALIDARG;
                                } else if (::IsEqualIID(p_IID, IntfIID)) {
                                    *p_ppObject = static_cast<IntfT*>(this);
                                    res = S_OK;
                                } else {
                                    res = BaseT::QueryInterface(p_IID, p_ppObject);
                                }

                                return res;
                            }

    private:
        // THESE METHODS ARE NOT IMPLEMENTED.
                            Object(const Object&);
        Object&             operator=(const Object&);
    };

}; // namespace COM
}; // namespace RRO


#endif RRO_UTILS_COM_OBJECT_H
